Põhjalik ülevaade JavaScripti asünkroonse iteraatori abilisest 'scan', selle funktsionaalsusest, kasutusjuhtudest ja eelistest asünkroonsel andmetöötlusel.
JavaScript'i asünkroonse iteraatori abiline: Scan - asünkroonne akumuleeriv töötlus
Asünkroonne programmeerimine on kaasaegse JavaScripti arenduse nurgakivi, eriti kui tegemist on I/O-ga seotud toimingutega, nagu võrgupäringud või failisüsteemi interaktsioonid. Asünkroonsed iteraatorid, mis võeti kasutusele ES2018-s, pakuvad võimsat mehhanismi asünkroonsete andmevoogude käsitlemiseks. scan-abiline, mida sageli leidub teekides nagu RxJS ja mis on üha enam saadaval iseseisva utiliidina, avab veelgi rohkem potentsiaali nende asünkroonsete andmevoogude töötlemiseks.
Asünkroonsete iteraatorite mõistmine
Enne scan'i süvenemist tuletame meelde, mis on asünkroonsed iteraatorid. Asünkroonne iteraator on objekt, mis vastab asünkroonse iteraatori protokollile. See protokoll defineerib next() meetodi, mis tagastab lubaduse (promise), mis laheneb objektiks, millel on kaks omadust: value (järjestuse järgmine väärtus) ja done (tõeväärtus, mis näitab, kas iteraator on lõpetanud). Asünkroonsed iteraatorid on eriti kasulikud töötamisel andmetega, mis saabuvad aja jooksul, või andmetega, mille hankimiseks on vaja asünkroonseid toiminguid.
Siin on põhiline näide asünkroonsest iteraatorist:
async function* generateNumbers() {
yield 1;
yield 2;
yield 3;
}
async function main() {
const iterator = generateNumbers();
let result = await iterator.next();
console.log(result); // { value: 1, done: false }
result = await iterator.next();
console.log(result); // { value: 2, done: false }
result = await iterator.next();
console.log(result); // { value: 3, done: false }
result = await iterator.next();
console.log(result); // { value: undefined, done: true }
}
main();
scan abilise tutvustus
scan-abiline (tuntud ka kui accumulate või reduce) teisendab asünkroonse iteraatori, rakendades igale väärtusele akumulaatorfunktsiooni ja väljastades akumuleeritud tulemuse. See on analoogne massiivide reduce-meetodiga, kuid töötab asünkroonselt ja iteraatoritega.
Sisuliselt võtab scan asünkroonse iteraatori, akumulaatorfunktsiooni ja valikulise algväärtuse. Iga lähteiteraatori poolt väljastatud väärtuse puhul kutsutakse akumulaatorfunktsioon välja eelmise akumuleeritud väärtusega (või algväärtusega, kui tegemist on esimese iteratsiooniga) ja iteraatori praeguse väärtusega. Akumulaatorfunktsiooni tulemusest saab järgmine akumuleeritud väärtus, mis seejärel väljastatakse tulemuseks oleva asünkroonse iteraatori poolt.
Süntaks ja parameetrid
scan'i kasutamise üldine süntaks on järgmine:
async function* scan(sourceIterator, accumulator, initialValue) {
let accumulatedValue = initialValue;
for await (const value of sourceIterator) {
accumulatedValue = accumulator(accumulatedValue, value);
yield accumulatedValue;
}
}
sourceIterator: Teisendatav asünkroonne iteraator.accumulator: Funktsioon, mis võtab kaks argumenti: eelmine akumuleeritud väärtus ja praegune väärtus iteraatorist. See peaks tagastama uue akumuleeritud väärtuse.initialValue(valikuline): Akumulaatori algväärtus. Kui seda ei pakuta, kasutatakse algväärtusena lähteiteraatori esimest väärtust ja akumulaatorfunktsiooni hakatakse kutsuma alates teisest väärtusest.
Kasutusjuhud ja näited
scan-abiline on uskumatult mitmekülgne ja seda saab kasutada laias valikus stsenaariumides, mis hõlmavad asünkroonseid andmevooge. Siin on mõned näited:
1. Jooksva kogusumma arvutamine
Kujutage ette, et teil on asünkroonne iteraator, mis väljastab tehingute summasid. Saate kasutada scan'i nende tehingute jooksva kogusumma arvutamiseks.
async function* generateTransactions() {
yield 10;
yield 20;
yield 30;
}
async function main() {
const transactions = generateTransactions();
const runningTotals = scan(transactions, (acc, value) => acc + value, 0);
for await (const total of runningTotals) {
console.log(total); // Väljund: 10, 30, 60
}
}
main();
Selles näites liidab accumulator-funktsioon lihtsalt praeguse tehingu summa eelmisele kogusummale. initialValue 0 tagab, et jooksev kogusumma algab nullist.
2. Andmete kogumine massiivi
Saate kasutada scan'i andmete kogumiseks asünkroonsest iteraatorist massiivi. See võib olla kasulik andmete kogumiseks aja jooksul ja nende töötlemiseks partiidena.
async function* fetchData() {
yield { id: 1, name: 'Alice' };
yield { id: 2, name: 'Bob' };
yield { id: 3, name: 'Charlie' };
}
async function main() {
const dataStream = fetchData();
const accumulatedData = scan(dataStream, (acc, value) => [...acc, value], []);
for await (const data of accumulatedData) {
console.log(data); // Väljund: [{id: 1, name: 'Alice'}], [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}], [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}, {id: 3, name: 'Charlie'}]
}
}
main();
Siin kasutab accumulator-funktsioon laialilaotamise operaatorit (`...`), et luua uus massiiv, mis sisaldab kõiki eelmisi elemente ja praegust väärtust. initialValue on tühi massiiv.
3. Päringute piiraja (Rate Limiter) implementeerimine
Keerulisem kasutusjuht on päringute piiraja implementeerimine. Saate kasutada scan'i, et jälgida teatud ajaaknas tehtud päringute arvu ja viivitada järgmiste päringutega, kui piirang ületatakse.
async function* generateRequests() {
// Simuleerime sissetulevaid päringuid
yield Date.now();
await new Promise(resolve => setTimeout(resolve, 200));
yield Date.now();
await new Promise(resolve => setTimeout(resolve, 100));
yield Date.now();
}
async function main() {
const requests = generateRequests();
const rateLimitWindow = 1000; // 1 sekund
const maxRequestsPerWindow = 2;
async function* rateLimitedRequests(source, window, maxRequests) {
let queue = [];
for await (const requestTime of source) {
queue.push(requestTime);
queue = queue.filter(t => requestTime - t < window);
if (queue.length > maxRequests) {
const earliestRequest = queue[0];
const delay = window - (requestTime - earliestRequest);
console.log(`Päringute limiit ületatud. Viivitus ${delay}ms`);
await new Promise(resolve => setTimeout(resolve, delay));
}
yield requestTime;
}
}
const limited = rateLimitedRequests(requests, rateLimitWindow, maxRequestsPerWindow);
for await (const requestTime of limited) {
console.log(`Päring töödeldud ajal ${requestTime}`);
}
}
main();
See näide kasutab sisemiselt scan'i (rateLimitedRequests funktsioonis), et hoida päringute ajatemplite järjekorda. See kontrollib, kas päringute arv piirangu ajaaknas ületab maksimaalselt lubatud arvu. Kui ületab, arvutab see vajaliku viivituse ja peatab enne päringu väljastamist.
4. Reaalajas andmete koondaja ehitamine (globaalne näide)
Kujutage ette globaalset finantsrakendust, mis peab koondama reaalajas aktsiahindu erinevatelt börsidelt. Asünkroonne iteraator võiks voogedastada hinnauuendusi börsidelt nagu New Yorgi börs (NYSE), Londoni börs (LSE) ja Tokyo börs (TSE). scan'i saab kasutada konkreetse aktsia jooksva keskmise või kõrgeima/madalaima hinna hoidmiseks kõigil börsidel.
// Simuleerime aktsiahindade voogedastust erinevatelt börsidelt
async function* generateStockPrices() {
yield { exchange: 'NYSE', symbol: 'AAPL', price: 170.50 };
yield { exchange: 'LSE', symbol: 'AAPL', price: 170.75 };
await new Promise(resolve => setTimeout(resolve, 50));
yield { exchange: 'TSE', symbol: 'AAPL', price: 170.60 };
}
async function main() {
const stockPrices = generateStockPrices();
// Kasutame scan'i jooksva keskmise hinna arvutamiseks
const runningAverages = scan(
stockPrices,
(acc, priceUpdate) => {
const { total, count } = acc;
return { total: total + priceUpdate.price, count: count + 1 };
},
{ total: 0, count: 0 }
);
for await (const averageData of runningAverages) {
const averagePrice = averageData.total / averageData.count;
console.log(`Jooksev keskmine hind: ${averagePrice.toFixed(2)}`);
}
}
main();
Selles näites arvutab accumulator-funktsioon hindade jooksvat kogusummat ja saadud uuenduste arvu. Lõplik keskmine hind arvutatakse seejärel nendest akumuleeritud väärtustest. See annab reaalajas ülevaate aktsia hinnast erinevatel globaalsetel turgudel.
5. Veebisaidi liikluse globaalne analüüs
Kujutage ette globaalset veebianalüütika platvormi, mis võtab vastu veebisaidi külastuste andmevooge serveritest üle maailma. Iga andmepunkt esindab kasutajat, kes külastab veebisaiti. Kasutades scan'i, saame reaalajas analüüsida lehevaatamiste trendi riikide kaupa. Oletame, et andmed näevad välja sellised: { country: "US", page: "homepage", timestamp: 1678886400 }.
async function* generateWebsiteVisits() {
yield { country: 'US', page: 'homepage', timestamp: Date.now() };
yield { country: 'CA', page: 'product', timestamp: Date.now() };
yield { country: 'UK', page: 'blog', timestamp: Date.now() };
yield { country: 'US', page: 'product', timestamp: Date.now() };
}
async function main() {
const visitStream = generateWebsiteVisits();
const pageViewCounts = scan(
visitStream,
(acc, visit) => {
const { country } = visit;
const newAcc = { ...acc };
newAcc[country] = (newAcc[country] || 0) + 1;
return newAcc;
},
{}
);
for await (const counts of pageViewCounts) {
console.log('Lehevaatamiste arv riikide kaupa:', counts);
}
}
main();
Siin uuendab accumulator-funktsioon iga riigi loendurit. Väljund näitaks iga riigi akumuleeruvaid lehevaatamiste arve uute külastusandmete saabumisel.
scan'i kasutamise eelised
scan-abiline pakub mitmeid eeliseid asünkroonsete andmevoogudega töötamisel:
- Deklaratiivne stiil:
scanvõimaldab teil väljendada akumuleeriva töötluse loogikat deklaratiivsel ja lühidal viisil, parandades koodi loetavust ja hooldatavust. - Asünkroonne käsitlemine: See käsitleb sujuvalt asünkroonseid operatsioone akumulaatorfunktsioonis, muutes selle sobivaks keerukate stsenaariumide jaoks, mis hõlmavad I/O-ga seotud ülesandeid.
- Reaalajas töötlemine:
scanvõimaldab andmevoogude reaalajas töötlemist, mis lubab teil reageerida muudatustele nende toimumise hetkel. - Kompositsioonilisus: Seda saab kergesti kombineerida teiste asünkroonsete iteraatorite abilistega, et luua keerukaid andmetöötlustorusid.
scan'i implementeerimine (kui see pole saadaval)
Kuigi mõned teegid pakuvad sisseehitatud scan-abilist, saate vajadusel hõlpsasti omaenda implementeerida. Siin on lihtne teostus:
async function* scan(sourceIterator, accumulator, initialValue) {
let accumulatedValue = initialValue;
let first = true;
for await (const value of sourceIterator) {
if (first && initialValue === undefined) {
accumulatedValue = value;
first = false;
} else {
accumulatedValue = accumulator(accumulatedValue, value);
}
yield accumulatedValue;
}
}
See implementatsioon itereerib üle lähteiteraatori ja rakendab igale väärtusele akumulaatorfunktsiooni, väljastades akumuleeritud tulemuse. See käsitleb juhtumit, kus initialValue'd ei ole antud, kasutades algväärtusena lähteiteraatori esimest väärtust.
Võrdlus reduce'iga
On oluline eristada scan'i ja reduce'i. Kuigi mõlemad töötavad iteraatoritega ja kasutavad akumulaatorfunktsiooni, erinevad nad oma käitumise ja väljundi poolest.
scanväljastab akumuleeritud väärtuse iga iteratsiooni kohta, pakkudes akumulatsiooni jooksvat ajalugu.reduceväljastab ainult lõpliku akumuleeritud väärtuse pärast kõigi elementide töötlemist iteraatoris.
Seetõttu sobib scan stsenaariumideks, kus peate jälgima akumulatsiooni vahepealseid seisundeid, samas kui reduce on sobilik siis, kui vajate ainult lõpptulemust.
Veatöötlus
Asünkroonsete iteraatorite ja scan'iga töötades on oluline vigu korrektselt käsitleda. Vead võivad tekkida iteratsiooniprotsessi käigus või akumulaatorfunktsioonis. Nende vigade püüdmiseks ja käsitlemiseks saate kasutada try...catch plokke.
async function* generatePotentiallyFailingData() {
yield 1;
yield 2;
throw new Error('Midagi läks valesti!');
yield 3;
}
async function main() {
const dataStream = generatePotentiallyFailingData();
try {
const accumulatedData = scan(dataStream, (acc, value) => acc + value, 0);
for await (const data of accumulatedData) {
console.log(data);
}
} catch (error) {
console.error('Tekkis viga:', error);
}
}
main();
Selles näites püüab try...catch plokk kinni vea, mille viskab generatePotentiallyFailingData iteraator. Seejärel saate viga vastavalt käsitleda, näiteks logides selle või proovides toimingut uuesti.
Kokkuvõte
scan-abiline on võimas tööriist asünkroonse akumuleeriva töötluse teostamiseks JavaScripti asünkroonsete iteraatoritega. See võimaldab teil väljendada keerukaid andmeteisendusi deklaratiivsel ja lühidal viisil, käsitleda asünkroonseid operatsioone sujuvalt ja töödelda andmevooge reaalajas. Mõistes selle funktsionaalsust ja kasutusjuhte, saate scan'i abil ehitada robustsemaid ja tõhusamaid asünkroonseid rakendusi. Olgu tegemist jooksvate kogusummade arvutamise, andmete kogumisega massiividesse, päringute piirajate implementeerimise või reaalajas andmete koondajate ehitamisega, scan võib lihtsustada teie koodi ja parandada selle üldist jõudlust. Pidage meeles arvestada veatöötlusega ja valida scan reduce'i asemel, kui vajate juurdepääsu vahepealsetele akumuleeritud väärtustele oma asünkroonsete andmevoogude töötlemise ajal. Teekide nagu RxJS uurimine võib veelgi süvendada teie arusaama ja praktilist rakendust scan'ist reaktiivse programmeerimise paradigmades.